Skip to content

feat: map-based LLM provider config with pricing dialog - #3492

Merged
JanCizmar merged 7 commits into
mainfrom
jancizmar/map-based-llm-config
Mar 12, 2026
Merged

feat: map-based LLM provider config with pricing dialog#3492
JanCizmar merged 7 commits into
mainfrom
jancizmar/map-based-llm-config

Conversation

@JanCizmar

@JanCizmar JanCizmar commented Feb 25, 2026

Copy link
Copy Markdown
Member

Summary

  • Add map-based LLM provider configuration support
  • Add pricing info dialog for server-configured LLM providers showing token costs (MT credits) and estimated translation costs for 1,000 strings
  • Add descriptive text labels above pricing tables

Test plan

  • Verify pricing dialog opens from info icon on server providers
  • Verify token prices and estimates display correctly
  • Verify EUR column shows when billing is active
  • Run billing e2e tests (see billing PR)

Summary by CodeRabbit

  • New Features

    • Provider pricing UI: info button, modal, compact token-price and cost-estimate tables with EUR conversion.
    • Pricing displayed only when billing is enabled.
    • Provider configuration supports per-provider defaults for easier setup.
    • Provider schema includes optional input/output token price fields.
  • Tests

    • Unit tests covering provider-defaults merging.
    • E2E test ensuring pricing is hidden when billing is disabled.
  • Documentation

    • Updated API workflow and examples.

@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds provider-defaults support and token pricing fields across backend models, service merging logic, unit/e2e tests, generated API schemas, and frontend UI components to display pricing when billing is enabled.

Changes

Cohort / File(s) Summary
Backend: model & assembler
backend/api/src/main/kotlin/io/tolgee/hateoas/llmProvider/LlmProviderSimpleModel.kt, ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/LlmProviderSimpleModelAssembler.kt
Added nullable tokenPriceInCreditsInput and tokenPriceInCreditsOutput to the simple provider model and assembler wiring.
Backend: properties & defaults
backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt
Introduced providerDefaults: MutableMap<String, LlmProviderDefaults> and a LlmProviderDefaults class (includes pricing fields) with toLlmProvider(name) helper.
Backend: merging service & tests
backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt, backend/data/src/test/kotlin/io/tolgee/unit/LlmPropertiesServiceMergeTest.kt
Added getMergedProviders() and mergeProviderWithDefaults() to overlay list entries onto map defaults; getProviders() delegates to merged result; comprehensive unit tests for merging behavior and precedence.
Frontend: pricing UI components
webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx, webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx, webapp/src/ee/llm/OrganizationLLMProviders/TokenPricesTable.tsx, webapp/src/ee/llm/OrganizationLLMProviders/EstimateCostTable.tsx
New components to render token prices, estimate costs, and a pricing dialog/info UI; exports types and formatting helpers; conditional EUR column when billing data present.
Frontend: providers view & server list
webapp/src/ee/llm/OrganizationLLMProviders/OrganizationLlmProvidersView.tsx, webapp/src/ee/llm/OrganizationLLMProviders/LlmProvidersServer.tsx
Replaced fixed data-cy with per-item value; LlmProvidersServer queries billing subscription, computes perThousandMtCredits, adjusts layout columns, and conditionally renders pricing info.
Frontend: generated API & billing schema
webapp/src/service/apiSchema.generated.ts, webapp/src/service/billingApiSchema.generated.ts
Added optional tokenPriceInCreditsInput / tokenPriceInCreditsOutput to LlmProviderSimpleModel; expanded billing stats endpoints and paged stats models in generated billing schema.
E2E & test types
e2e/cypress/e2e/llmProviders/llmProviders.cy.ts, e2e/cypress/support/dataCyType.d.ts
Added e2e test asserting pricing hidden when billing disabled; updated data-cy literals (added pricing-related keys, removed old tab literal).
Docs
webapp/CLAUDE.md
Rewrote and reorganized API usage workflow and payload examples; clarified pre-call requirements, screenshot/tagging guidance, and payload format.

Sequence Diagram(s)

sequenceDiagram
    participant Config as LlmProperties (providers + providerDefaults)
    participant Service as LlmPropertiesService
    participant Assembler as LlmProviderSimpleModelAssembler
    participant UI as Frontend (Providers View)
    participant Billing as Billing API

    Config->>Service: getMergedProviders()
    Service->>Service: mergeProviderWithDefaults() per provider entry
    Service-->>Assembler: merged LlmProvider(s) (incl. tokenPrice fields)
    Assembler-->>UI: serve LlmProviderSimpleModel
    UI->>Billing: fetch subscription (billingEnabled, credits)
    Billing-->>UI: perThousandMtCredits
    UI->>UI: render TokenPricesTable / EstimateCostTable / PricingDialog when billingEnabled and pricing present
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

keep-open

Suggested reviewers

  • dkrizan
  • gabrielshanahan

Poem

🐰 Defaults tucked in maps and lists I sow,
Prices for input and output now show,
Services stitch values, tests hop through night,
UI pops a dialog when billing’s in sight,
The rabbit cheers — pricing hops just so!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: map-based LLM provider config with pricing dialog' accurately captures the main changes: adding map-based LLM provider configuration and a pricing dialog component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jancizmar/map-based-llm-config

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webapp/CLAUDE.md (1)

80-105: ⚠️ Potential issue | 🟠 Major

Add explicit EXPECT_NO_CONFLICT resolution to the import example to prevent accidental key overwrites.

The single-step-import-resolvable endpoint example (lines 80–105) should include the conflict resolution strategy. Without it, the API may behave unpredictably if a key already exists in the project.

Suggested doc update
 curl -X POST "https://app.tolgee.io/v2/projects/single-step-import-resolvable" \
   -H "X-API-Key: ${VITE_APP_TOLGEE_API_KEY}" \
   -H "Content-Type: application/json" \
   -d '{
+    "resolution": "EXPECT_NO_CONFLICT",
     "keys": [
       {
         "name": "my_key",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/CLAUDE.md` around lines 80 - 105, Update the
single-step-import-resolvable example to include an explicit conflict resolution
field by adding "resolution": "EXPECT_NO_CONFLICT" at the top-level of the POST
body for the POST
https://app.tolgee.io/v2/projects/single-step-import-resolvable example; modify
the JSON payload shown in the example so that the request always specifies
resolution: "EXPECT_NO_CONFLICT" (so key creation via the "keys" array will fail
when a conflicting key already exists), keeping the rest of the payload
structure (keys, translations, tags, screenshots) unchanged.
🧹 Nitpick comments (2)
backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt (1)

29-31: Nit: prefer .any {} over .find {} != null.

Proposed fix
-    val hasTolgeeConfig = result.find { it.type == LlmProviderType.TOLGEE } != null
+    val hasTolgeeConfig = result.any { it.type == LlmProviderType.TOLGEE }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt`
around lines 29 - 31, Replace the usage of result.find { it.type ==
LlmProviderType.TOLGEE } != null with a more idiomatic check using result.any {
it.type == LlmProviderType.TOLGEE } inside LlmPropertiesService where
getMergedProviders() is assigned to result and subscriptionActive() is checked;
update the hasTolgee assignment accordingly to use .any for readability and
performance.
webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx (1)

198-301: Consider extracting the repeated estimate row pattern to reduce duplication.

The three estimate table rows (lines 199–232, 233–266, 267–300) share identical structure, differing only in the data-cy value, label key, and input token constant. This could be extracted into a small helper component or mapped over an array.

Example refactor
const ESTIMATES = [
  {
    cy: 'llm-provider-pricing-estimate-context-and-screenshots',
    labelKey: 'llm_provider_pricing_context_and_screenshots',
    labelDefault: 'Context + screenshots',
    inputTokens: INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS,
  },
  {
    cy: 'llm-provider-pricing-estimate-context-no-screenshots',
    labelKey: 'llm_provider_pricing_context_no_screenshots',
    labelDefault: 'Context, no screenshots',
    inputTokens: INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS,
  },
  {
    cy: 'llm-provider-pricing-estimate-no-context',
    labelKey: 'llm_provider_pricing_no_context',
    labelDefault: 'No context',
    inputTokens: INPUT_TOKENS_NO_CONTEXT,
  },
] as const;

// Then in the TableBody:
{ESTIMATES.map(({ cy, labelKey, labelDefault, inputTokens }) => (
  <TableRow key={cy} data-cy={cy}>
    <TableCell><T keyName={labelKey} defaultValue={labelDefault} /></TableCell>
    <TableCell align="right">{'~'}{formatCredits(inputTokens + OUTPUT_TOKENS)}</TableCell>
    <TableCell align="right">{'~'}{formatCredits(estimateCredits(inputTokens, OUTPUT_TOKENS)!)}</TableCell>
    {pricePerMtCredit != null && (
      <TableCell align="right">{'~'}{creditsToEur(estimateCredits(inputTokens, OUTPUT_TOKENS)!)}</TableCell>
    )}
  </TableRow>
))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`
around lines 198 - 301, The three nearly identical rows (TableRow with data-cy
values llm-provider-pricing-estimate-context-and-screenshots,
llm-provider-pricing-estimate-context-no-screenshots,
llm-provider-pricing-estimate-no-context) duplicate markup; extract them into a
small reusable mapping or helper component (e.g., an ESTIMATES array and map
over it) that supplies data-cy, label key/default, and inputTokens
(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS,
INPUT_TOKENS_NO_CONTEXT) and then render the shared cells using formatCredits,
estimateCredits, creditsToEur and the pricePerMtCredit conditional to remove
repetition while preserving behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/cypress/e2e/llmProviders/llmProviders.cy.ts`:
- Around line 103-107: Replace the text-based tab click in the test by targeting
the Server tab via its data-cy selector instead of using
gcy('organization-llm-providers-tab').contains('Server').click(); specifically
change the interaction to use the dedicated selector (e.g.,
gcy('organization-llm-providers-server-tab').click()) so the test uses the
data-cy-driven tab switch and then keep the assertions on
gcy('llm-provider-item-name') and gcy('llm-provider-pricing-info') as-is.

In `@webapp/CLAUDE.md`:
- Line 16: Change the absolute instruction "**Important:** Remember to upload
the context (BigMeta) and Screenshots for each key." to a conditional note that
BigMeta uploads are required only for multi-key changes and that BigMeta
requires a minimum of two related keys; update the related guidance around the
"BigMeta requires at least 2 keys" wording (the block referencing BigMeta and
Screenshots) so it states "Upload context (BigMeta) for related keys with a
minimum of 2 related keys required" and adjust the examples/lines that currently
say "always upload" (the BigMeta/Screenshots guidance) to make clear single-key
changes do not require a BigMeta upload.

In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 206-211: The Tokens cell currently shows per-string tokens (using
INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) while the header is
"Estimated cost for 1,000 strings"; update the Tokens column to show the total
for STRINGS_COUNT by multiplying the token expression by STRINGS_COUNT (e.g.,
(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) * STRINGS_COUNT) and pass
that value to the same formatting utility you use for credits (or create/ reuse
a formatTokens helper) so Tokens and Credits are both presented on the same
1,000-strings basis; alternatively, if you prefer per-string display, change the
column header to include "per string" instead of modifying the value.
- Around line 16-19: The import statement for useMoneyFormatter and
useNumberFormatter is misformatted for Prettier; replace the grouped multi-line
import with a single-line import: import { useMoneyFormatter, useNumberFormatter
} from 'tg.hooks/useLocale'; so that the symbols useMoneyFormatter and
useNumberFormatter are imported together on one line and adhere to
Prettier/linters.
- Around line 144-153: Prettier flags formatting problems in
LlmProviderPricingDialog.tsx around the TableCell lines showing
formatCredits(inputPrice!) and formatCredits(outputPrice!); open the
LlmProviderPricingDialog.tsx file and run the project formatter (e.g., npm/yarn
prettierrc or the repository's format script) or apply Prettier formatting to
the block containing the TableRow/TableCell/T components so spacing and line
breaks match project style, then save/commit the changes; verify the formatted
symbols are TableCell, formatCredits, inputPrice, outputPrice and the T
component remain unchanged functionally.

---

Outside diff comments:
In `@webapp/CLAUDE.md`:
- Around line 80-105: Update the single-step-import-resolvable example to
include an explicit conflict resolution field by adding "resolution":
"EXPECT_NO_CONFLICT" at the top-level of the POST body for the POST
https://app.tolgee.io/v2/projects/single-step-import-resolvable example; modify
the JSON payload shown in the example so that the request always specifies
resolution: "EXPECT_NO_CONFLICT" (so key creation via the "keys" array will fail
when a conflicting key already exists), keeping the rest of the payload
structure (keys, translations, tags, screenshots) unchanged.

---

Nitpick comments:
In `@backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt`:
- Around line 29-31: Replace the usage of result.find { it.type ==
LlmProviderType.TOLGEE } != null with a more idiomatic check using result.any {
it.type == LlmProviderType.TOLGEE } inside LlmPropertiesService where
getMergedProviders() is assigned to result and subscriptionActive() is checked;
update the hasTolgee assignment accordingly to use .any for readability and
performance.

In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 198-301: The three nearly identical rows (TableRow with data-cy
values llm-provider-pricing-estimate-context-and-screenshots,
llm-provider-pricing-estimate-context-no-screenshots,
llm-provider-pricing-estimate-no-context) duplicate markup; extract them into a
small reusable mapping or helper component (e.g., an ESTIMATES array and map
over it) that supplies data-cy, label key/default, and inputTokens
(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS,
INPUT_TOKENS_NO_CONTEXT) and then render the shared cells using formatCredits,
estimateCredits, creditsToEur and the pricePerMtCredit conditional to remove
repetition while preserving behavior.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 007f2f6 and bae008b.

⛔ Files ignored due to path filters (2)
  • e2e/package-lock.json is excluded by !**/package-lock.json
  • library/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/llmProvider/LlmProviderSimpleModel.kt
  • backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt
  • backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt
  • backend/data/src/test/kotlin/io/tolgee/unit/LlmPropertiesServiceMergeTest.kt
  • e2e/cypress/e2e/llmProviders/llmProviders.cy.ts
  • e2e/cypress/support/dataCyType.d.ts
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/LlmProviderSimpleModelAssembler.kt
  • webapp/CLAUDE.md
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProvidersServer.tsx
  • webapp/src/service/apiSchema.generated.ts
  • webapp/src/service/billingApiSchema.generated.ts

Comment thread e2e/cypress/e2e/llmProviders/llmProviders.cy.ts
Comment thread webapp/CLAUDE.md Outdated
Comment thread webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx Outdated
Comment thread webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx Outdated
Comment thread webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx (1)

207-211: Tokens unit is still inconsistent with the “1,000 strings” estimate table.

The Tokens column shows per-string token counts, while Credits/EUR are totals for 1,000 strings. Keep units aligned (multiply Tokens by STRINGS_COUNT or relabel as per-string).

Also applies to: 241-245, 275-277

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`
around lines 207 - 211, The Tokens column displays per-string token counts but
Credits/EUR are totals for 1,000 strings; to align units, multiply the token
total by STRINGS_COUNT where the component renders token totals (e.g., the
expression INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS inside the
TableCell and the similar expressions at the other occurrences) before passing
to formatCredits or rendering, i.e., replace
formatCredits(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) with
formatCredits((INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) *
STRINGS_COUNT) so Tokens and Credits/EUR both represent totals for STRINGS_COUNT
strings.
🧹 Nitpick comments (1)
webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx (1)

213-294: Compute each scenario estimate once to reduce duplication.

Each row recalculates estimateCredits(...) for both Credits and EUR cells. Precomputing per-row estimates improves readability and reduces repeated non-null assertions.

♻️ Refactor sketch
+  const estimateContextAndScreenshots = estimateCredits(
+    INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS,
+    OUTPUT_TOKENS
+  );
+  const estimateContextNoScreenshots = estimateCredits(
+    INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS,
+    OUTPUT_TOKENS
+  );
+  const estimateNoContext = estimateCredits(
+    INPUT_TOKENS_NO_CONTEXT,
+    OUTPUT_TOKENS
+  );
...
-                      {formatCredits(
-                        estimateCredits(
-                          INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS,
-                          OUTPUT_TOKENS
-                        )!
-                      )}
+                      {formatCredits(estimateContextAndScreenshots!)}
...
-                        {creditsToEur(
-                          estimateCredits(
-                            INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS,
-                            OUTPUT_TOKENS
-                          )!
-                        )}
+                        {creditsToEur(estimateContextAndScreenshots!)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`
around lines 213 - 294, Rows repeatedly call estimateCredits(...) (with non-null
assertions) for the Credits and EUR cells; compute each row's estimate once into
a local const (e.g. const estimate = estimateCredits(INPUT_TOKENS_...,
OUTPUT_TOKENS)) and use that variable in both formatCredits(estimate) and
creditsToEur(estimate) (guard for null/undefined or use conditional rendering
based on pricePerMtCredit), replacing repeated estimateCredits(...) calls and
removing duplicated non-null assertions; apply this pattern for the rows using
INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, and
INPUT_TOKENS_NO_CONTEXT and keep existing symbols like formatCredits,
creditsToEur, and pricePerMtCredit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 207-211: The Tokens column displays per-string token counts but
Credits/EUR are totals for 1,000 strings; to align units, multiply the token
total by STRINGS_COUNT where the component renders token totals (e.g., the
expression INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS inside the
TableCell and the similar expressions at the other occurrences) before passing
to formatCredits or rendering, i.e., replace
formatCredits(INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) with
formatCredits((INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS + OUTPUT_TOKENS) *
STRINGS_COUNT) so Tokens and Credits/EUR both represent totals for STRINGS_COUNT
strings.

---

Nitpick comments:
In `@webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx`:
- Around line 213-294: Rows repeatedly call estimateCredits(...) (with non-null
assertions) for the Credits and EUR cells; compute each row's estimate once into
a local const (e.g. const estimate = estimateCredits(INPUT_TOKENS_...,
OUTPUT_TOKENS)) and use that variable in both formatCredits(estimate) and
creditsToEur(estimate) (guard for null/undefined or use conditional rendering
based on pricePerMtCredit), replacing repeated estimateCredits(...) calls and
removing duplicated non-null assertions; apply this pattern for the rows using
INPUT_TOKENS_CONTEXT_AND_SCREENSHOTS, INPUT_TOKENS_CONTEXT_NO_SCREENSHOTS, and
INPUT_TOKENS_NO_CONTEXT and keep existing symbols like formatCredits,
creditsToEur, and pricePerMtCredit.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bae008b and 3580129.

📒 Files selected for processing (1)
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
webapp/CLAUDE.md (1)

107-108: Clarify repeated keys guidance for better precision.

The statement "A key appearing multiple times (e.g. repeated buttons) should have multiple entries in positions" could be misinterpreted. It's unclear whether this means multiple position objects within the positions array of a single key, or multiple key entries in the keys array.

📝 Suggested clarification
-Map each entry from `getVisibleKeys()` to a key in the `keys` array, using the `position` values for `positions`.
-A key appearing multiple times (e.g. repeated buttons) should have multiple entries in `positions`.
+Map each entry from `getVisibleKeys()` to a key in the `keys` array, using the `position` values for `positions`.
+If the same key appears multiple times on the page (e.g. repeated buttons), add multiple position objects to the `positions` array within a single `screenshots` entry.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/CLAUDE.md` around lines 107 - 108, The wording is ambiguous about
repeated physical keys; update the guidance in CLAUDE.md so it explicitly states
the mapping: for each entry returned by getVisibleKeys() create (or map to) a
single key object in the keys array and collect all physical occurrences of that
key as multiple position objects inside that key object's positions array (do
not create duplicate key entries in keys). Mention the alternative only if the
design requires distinct logical keys (i.e., create multiple keys in keys
array), but default to the single-key-with-multiple-positions approach and show
the exact relationship between getVisibleKeys(), keys, and positions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@webapp/CLAUDE.md`:
- Around line 107-108: The wording is ambiguous about repeated physical keys;
update the guidance in CLAUDE.md so it explicitly states the mapping: for each
entry returned by getVisibleKeys() create (or map to) a single key object in the
keys array and collect all physical occurrences of that key as multiple position
objects inside that key object's positions array (do not create duplicate key
entries in keys). Mention the alternative only if the design requires distinct
logical keys (i.e., create multiple keys in keys array), but default to the
single-key-with-multiple-positions approach and show the exact relationship
between getVisibleKeys(), keys, and positions.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3580129 and 4baa426.

📒 Files selected for processing (7)
  • e2e/cypress/e2e/llmProviders/llmProviders.cy.ts
  • e2e/cypress/support/dataCyType.d.ts
  • webapp/CLAUDE.md
  • webapp/src/ee/llm/OrganizationLLMProviders/EstimateCostTable.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/OrganizationLlmProvidersView.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/TokenPricesTable.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/cypress/support/dataCyType.d.ts

@JanCizmar
JanCizmar requested a review from dkrizan February 26, 2026 12:45
Comment thread webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx Outdated
Comment thread backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt Outdated
@JanCizmar
JanCizmar requested a review from dkrizan March 11, 2026 12:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt (1)

174-179: Reuse LlmProvider.TYPE_DEFAULT in LlmProviderDefaults.

mergeProviderWithDefaults() uses LlmProvider.TYPE_DEFAULT as the sentinel for “Spring supplied the default”. Hard-coding OPENAI again here lets those two drift later and would silently change the merge behavior.

Suggested change
-    var type: LlmProviderType = LlmProviderType.OPENAI,
+    var type: LlmProviderType = LlmProvider.TYPE_DEFAULT,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`
around lines 174 - 179, LlmProviderDefaults currently hard-codes the default
provider type to LlmProviderType.OPENAI; change it to reuse the shared sentinel
LlmProvider.TYPE_DEFAULT so it stays consistent with mergeProviderWithDefaults.
Update the type property in class LlmProviderDefaults (and any constructor
defaults there) to use LlmProvider.TYPE_DEFAULT instead of
LlmProviderType.OPENAI, ensuring mergeProviderWithDefaults sees the same
sentinel value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`:
- Around line 71-78: Update the env-var examples in LlmProperties so they follow
Spring Boot's binding rules: use the canonical property
tolgee.llm.provider-defaults.gpt-5-mini.type and its env-var form
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE (no dashes/underscores between parts)
and note the map key will be lowercased (gpt5mini). Replace the incorrect
examples (e.g. TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE) with the correct
concatenated uppercase names for type/model and token price keys (e.g.
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINIMODEL,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSINPUT,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSOUTPUT) and mention the
map key expected is "gpt5mini".

---

Nitpick comments:
In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`:
- Around line 174-179: LlmProviderDefaults currently hard-codes the default
provider type to LlmProviderType.OPENAI; change it to reuse the shared sentinel
LlmProvider.TYPE_DEFAULT so it stays consistent with mergeProviderWithDefaults.
Update the type property in class LlmProviderDefaults (and any constructor
defaults there) to use LlmProvider.TYPE_DEFAULT instead of
LlmProviderType.OPENAI, ensuring mergeProviderWithDefaults sees the same
sentinel value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e0e865cf-ffcf-4ed8-b28c-367d3209092e

📥 Commits

Reviewing files that changed from the base of the PR and between 2330198 and 360a649.

📒 Files selected for processing (4)
  • backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt
  • backend/data/src/main/kotlin/io/tolgee/service/LlmPropertiesService.kt
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingInfo.tsx
  • webapp/src/ee/llm/OrganizationLLMProviders/LlmProviderPricingDialog.tsx

Comment on lines +71 to +78
or using environment variables (in a ConfigMap):

```
TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE=OPENAI
TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_MODEL=gpt-5-mini
TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TOKEN_PRICE_IN_CREDITS_INPUT=2.0
TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TOKEN_PRICE_IN_CREDITS_OUTPUT=1.5
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Spring Boot official documentation: how are environment variable names derived from kebab-case @ConfigurationProperties names, and how are Map keys bound from environment variables?

💡 Result:

Deriving environment-variable names from canonical (kebab-case) property names

Spring Boot recommends using the canonical form for property names (lowercase kebab-case, with . for nesting). To convert a canonical property name to an environment variable name, Spring Boot documents these rules: replace . with _, remove -, then uppercase. For example, spring.main.log-startup-info becomes SPRING_MAIN_LOGSTARTUPINFO. [1]

Binding Map keys from environment variables

When binding from environment variables, Spring Boot lowercases the environment variable name before binding. This usually doesn’t matter, except for Map bindings: Map keys produced from env vars are always lowercase. Example: if you have @ConfigurationProperties("my.props") with a Map<String,String> values, then setting MY_PROPS_VALUES_KEY=value results in values containing {"key"="value"} (and if MY_PROPS_VALUES_KEY=VALUE, the key is still "key" but the value is "VALUE"). [1]

Source: Spring Boot Reference Guide → Externalized Configuration → “Binding From Environment Variables” and “Binding Maps From Environment Variables”. [1]


🏁 Script executed:

cat -n backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt | head -100

Repository: tolgee/tolgee-platform

Length of output: 4100


🏁 Script executed:

cat -n backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt | sed -n '100,180p'

Repository: tolgee/tolgee-platform

Length of output: 3757


🏁 Script executed:

cat -n backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt | sed -n '174,200p'

Repository: tolgee/tolgee-platform

Length of output: 1531


🏁 Script executed:

rg -A 5 "TOLGEE_LLM_PROVIDER_DEFAULTS" --type kt --type java

Repository: tolgee/tolgee-platform

Length of output: 92


🏁 Script executed:

rg -A 5 "TOLGEE_LLM_PROVIDER_DEFAULTS"

Repository: tolgee/tolgee-platform

Length of output: 1294


🏁 Script executed:

# Let me verify the Spring Boot relaxed binding rules by checking any tests or integration examples
fd -e properties -e yml -e yaml | xargs grep -l "provider-defaults" 2>/dev/null | head -5

Repository: tolgee/tolgee-platform

Length of output: 48


Fix the provider-defaults env-var example.

Spring Boot derives env-var names by replacing . with _, removing - entirely, and uppercasing. For the canonical property tolgee.llm.provider-defaults.gpt-5-mini.type, the correct env-var is TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE (no underscores between parts). Additionally, Map keys bound from env vars are always lowercased, so the map key would be gpt5mini, not gpt-5-mini. The documented form TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE does not follow these rules and will fail to bind to the intended map entry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/machineTranslation/LlmProperties.kt`
around lines 71 - 78, Update the env-var examples in LlmProperties so they
follow Spring Boot's binding rules: use the canonical property
tolgee.llm.provider-defaults.gpt-5-mini.type and its env-var form
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE (no dashes/underscores between parts)
and note the map key will be lowercased (gpt5mini). Replace the incorrect
examples (e.g. TOLGEE_LLM_PROVIDER_DEFAULTS_GPT_5_MINI_TYPE) with the correct
concatenated uppercase names for type/model and token price keys (e.g.
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITYPE,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINIMODEL,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSINPUT,
TOLGEE_LLM_PROVIDERDEFAULTSGPT5MINITOKENPRICEINCREDITSOUTPUT) and mention the
map key expected is "gpt5mini".

JanCizmar and others added 7 commits March 11, 2026 19:56
Enable separating non-secret LLM provider config (model, prices, type)
from secrets (API keys) in Kubernetes deployments via a new
`provider-defaults` map that merges with the existing `providers` list.
Add a pricing information dialog for server-configured LLM providers.
Organization owners can now see token pricing in credits and estimated
costs for translating 1,000 strings.

- Extend LlmProviderSimpleModel with tokenPriceInCreditsInput/Output
- Create LlmProviderPricingDialog with token prices table, cost
  estimates (with/without screenshots), and info notes
- Create LlmProviderPricingInfo as self-contained icon + dialog
- Only show pricing info when billing is enabled
- Fetch subscription data to convert credits to USD
- Add E2E test verifying pricing info is hidden without billing
Add token pricing description and estimate description labels.
Update generated API schemas and data-cy types for pricing dialog.
- Split LlmProviderPricingDialog into smaller components
- Use data-cy from tab items instead of hardcoded selector
- Use llm-providers-server data-cy in e2e tests
- Clarify "Tokens" column as "Tokens (per string)"
- Fix BigMeta instruction wording in CLAUDE.md
…t TYPE_DEFAULT constant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@JanCizmar
JanCizmar force-pushed the jancizmar/map-based-llm-config branch from 360a649 to 48a5e82 Compare March 11, 2026 19:40
@JanCizmar
JanCizmar merged commit da858c0 into main Mar 12, 2026
102 of 109 checks passed
@JanCizmar
JanCizmar deleted the jancizmar/map-based-llm-config branch March 12, 2026 08:23
TolgeeMachine added a commit that referenced this pull request Mar 12, 2026
# [3.167.0](v3.166.3...v3.167.0) (2026-03-12)

### Features

* map-based LLM provider config with pricing dialog ([#3492](#3492)) ([da858c0](da858c0))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants